﻿using System;
using System.Drawing;
using Neurotec.Biometrics;
using Neurotec.Images;
using Neurotec.IO;


namespace AccessControl.FingerPrintLibrary
{

    public class Extraction
    {
        #region Field

        private int _defaultFingerprintWidth = 320;
        private NFExtractor _extractor;

        private static Extraction _extraction;
        #endregion

        #region Constructor & Singletone

        private Extraction()
        {

        }

        public static Extraction GetInstance()
        {
            return _extraction ?? (_extraction = new Extraction());
        }

        #endregion

        #region Public Method

        public Bitmap CreateImage(byte[] rawData)
        {
            return CreateImage(DefaultFingerprintWidth, rawData);
        }

        public Bitmap CreateImage(int width, byte[] rawData)
        {
            var height = rawData.Length / width;
            var bmp = new Bitmap(width, height);
            for (var y = 0; y < height; y++)
            {
                for (var x = 0; x < width; x++)
                {
                    var col = rawData[x + y * width];
                    bmp.SetPixel(x, y, Color.FromArgb(255, col, col, col));
                }
            }
            return bmp;
        }

        public byte[] Generalize(byte[][] rawBitmaps, out int G, out string errorMessage)
        {
            G = 0;
            errorMessage = null;
            try
            {
                var templates = new NFRecord[rawBitmaps.Length];
                var extractor = new NFExtractor();
                var templatesCount = 0;
                foreach (var rawImage in rawBitmaps)
                {
                    var bitmap = CreateImage(rawImage);
                    var image = NImage.FromBitmap(bitmap);
                    var grayscaleImage = GetGrayscaleImage(image, out errorMessage);
                    if (errorMessage != null) continue;

                    templates[templatesCount++] = ExtractFeatures(grayscaleImage,out G, out errorMessage);
                }

                if (errorMessage != null) return null;

                NfeExtractionStatus extractionStatus;
                int baseTemplateIndex;

                var generalizedTemplate = extractor.Generalize(templates, out extractionStatus, out baseTemplateIndex);

                if (extractionStatus != NfeExtractionStatus.TemplateCreated)
                {
                    errorMessage = "Failed generalizing templates";
                    return null;
                }

                G = generalizedTemplate.G;

                var buffer = generalizedTemplate.Save();
                return buffer.ToByteArray();

            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;
                return null;
            }
        }

        public byte[] GeneralizeTemplates(byte[][] Templates, out int G, out string errorMessage){
            G = 0;
            errorMessage = null;
            try
            {
                var extractor = new NFExtractor();
                NfeExtractionStatus extractionStatus;
                int baseTemplateIndex;

                var templates = new NFRecord[3];
                templates[0]=new NFRecord(Templates[0]);
                templates[1]=new NFRecord(Templates[1]);
                templates[2]=new NFRecord(Templates[2]);
                var generalizedTemplate = extractor.Generalize(templates, out extractionStatus, out baseTemplateIndex);

                if (extractionStatus != NfeExtractionStatus.TemplateCreated)
                {
                    errorMessage = "Failed generalizing templates";
                    return null;
                }

                G = generalizedTemplate.G;

                var buffer = generalizedTemplate.Save();
                return buffer.ToByteArray();

            }catch (Exception ex){
                errorMessage = ex.Message;
                return null;
            }
        }

        public byte[] ExtractToByte(byte[] rawBitmap, out int G, out string errorMessage)
        {
            var record = ExtractToNFRecord(rawBitmap, out G, out errorMessage);
            if (errorMessage != null) return null;
            G = record.G;
            return record.Save().ToByteArray();
        }

        public byte[] GetGrayscaleImage(byte[] rawBitmap, out string errorMessage)
        {
            try{
                var image = NImage.FromMemory(new NBuffer(rawBitmap));
                var grayscaleImage = GetGrayscaleImage(image, out errorMessage);
                if (errorMessage != null) return null;
                return grayscaleImage.Save(NImageFormat.Gif).ToByteArray();
            }
            catch (Exception ex){
                errorMessage = ex.Message;
            }
            return null;
        }

        #endregion

        #region Private Method

        private NGrayscaleImage GetGrayscaleImage(NImage coloredImage, out string errorMessage)
        {
            errorMessage = null;


            NGrayscaleImage _grayscaleImage;

            try
            {
                if (coloredImage != null)
                {
                    // convert image to grayscale
                    _grayscaleImage = coloredImage.ToGrayscale();
                    if (_grayscaleImage.ResolutionIsAspectRatio
                        || _grayscaleImage.HorzResolution < 250
                        || _grayscaleImage.VertResolution < 250)
                    {
                        _grayscaleImage.HorzResolution = 500;
                        _grayscaleImage.VertResolution = 500;
                        _grayscaleImage.ResolutionIsAspectRatio = false;
                    }
                    // extract a fingerprint template from the image for showing

                    //var bitmapStream = new NMemoryStream();
                    //_grayscaleImage.Save(bitmapStream,NImageFormat.Gif);
                    //return Image.FromStream(bitmapStream);

                    return _grayscaleImage;
                }
            }
            catch (Exception ex)
            {
                errorMessage = "Error creating grayscale image: " + ex.Message;
            }
            return null;
        }

        private NFRecord ExtractFeatures(NGrayscaleImage image,out int G, out string errorMessage){
            G = 0;
            errorMessage = null;
            if (_extractor == null)
                _extractor = new NFExtractor();

            NFRecord record = null;

            try
            {
                NfeExtractionStatus extractionStatus;
                record = _extractor.Extract(image, NFPosition.Unknown, NFImpressionType.LiveScanPlain,
                                           out extractionStatus);
                // extract a fingerprint template from the image to byte array for saving
                if (extractionStatus != NfeExtractionStatus.TemplateCreated)
                    errorMessage = "Fingerprint image is of low quality.";
                else G = record.G;
            }
            catch (Exception ex)
            {
                errorMessage = "Extraction error: " + ex.Message;
            }
            return record;
        }

        private NFRecord ExtractToNFRecord(byte[] rawBitmap, out int G, out string errorMessage)
        {
            var bitmap = CreateImage(rawBitmap);
            return ExtractNFRecord(bitmap, out G, out errorMessage);
        }

        private NFRecord ExtractNFRecord(Bitmap bitmap, out int G, out string errorMessage)
        {
            G = 0;
            var image = NImage.FromBitmap(bitmap);
            var grayscaleImage = GetGrayscaleImage(image, out errorMessage);
            return errorMessage != null ? null : ExtractFeatures(grayscaleImage,out G, out errorMessage);
        }

        #endregion

        #region Properties

        public int DefaultFingerprintWidth
        {
            get { return _defaultFingerprintWidth; }
            set { _defaultFingerprintWidth = value; }
        }

        #endregion


        public int GetG(byte[] template){
            var record = new NFRecord(template);
            return record.G;
        }
    }
}